using BepuPhysics;
using BepuPhysics.Collidables;
using BepuPhysics.CollisionDetection;
using BepuPhysics.Constraints;
using BepuUtilities.Memory;
using System;
using System.Runtime.CompilerServices;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif
namespace Demos.Demos
{
    /// <summary>
    /// 显示了在不使用任何其他演示类型的情况下完全孤立地使用引擎。
    /// </summary>
    public static class SimpleSelfContainedDemo
    {
        // 仿真具有各种必须定义的扩展点。
        // 这些演示倾向于重用一些类型,比如DemoNarrowPhaseCallback,但是这个演示将提供它自己的(超级简单)版本。
        // 如果您想知道为什么回调是接口实现结构,而不是类或事件,这是因为
        // 编译器可以使用编译时类型信息专门化实现。这避免了相关的分派开销
        // 使用委托或虚拟分派,并允许内联,这对于联系回调等频率极高的逻辑非常有用。
        unsafe struct NarrowPhaseCallbacks : INarrowPhaseCallbacks
        {
            /// <summary>
            /// 在构建仿真实例后执行任何所需的初始化逻辑。
            /// </summary>
            /// <param name="Simulation">拥有这些回调的模拟。</param>
            public void Initialize(Simulation simulation)
            {
                // 通常,回调类型是在模拟实例完全构造之前创建的,因此模拟将在准备好时调用此函数。
                // 任何依赖于现有模拟的逻辑都可以放在这里。
            }

            /// <summary>
            /// 选择是否允许为两个重叠的可碰撞对象继续生成接触。
            /// </summary>
            /// <param name="workerIndex">标识重叠的工作器的索引。</param>
            /// <param name="a">对对中第一个可冲突对象的引用。</param>
            /// <param name="b">对对中第二个可冲突对象的引用。</param>
            /// <return>如果应继续冲突检测,则为True;否则为False。</return>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b)
            {
                // 在创建窄阶段对之前,宽阶段会询问此回调是否要处理给定的对象对。
                // 这可用于实现任意形式的冲突过滤。有关示例,请参阅RagdollDemo或NewtDemo。
                // 在这里,我们将确保两个物体中至少有一个是动态的。
                // 引擎不会生成静电-静电对,但会生成运动学-运动学对。
                // 如果您正在尝试制作某种类型的传感器/触发器对象,这是很有用的,但是因为运动学-运动学对
                // 无法生成约束(两个实体都有无限惯性),简单的模拟只能忽略这些对。
                return a.Mobility == CollidableMobility.Dynamic || b.Mobility == CollidableMobility.Dynamic;
            }

            /// <summary>
            /// 选择是否允许为复合(包括)对中两个重叠的可碰撞对象的子项继续生成接触。
            /// </summary>
            /// <param name="Pair">两个子冲突项的父对。</param>
            /// <param name="Child IndexA">对中可碰撞A的子项的索引。如果可碰撞A不是复合的,则该值始终为0。</param>
            /// <param name="Child IndexB">对中可碰撞B的子项的索引。如果可碰撞B不是复合的,则该值始终为0。</param>
            /// <return>如果应继续冲突检测,则为True;否则为False。</return>
            /// <remarks>This is called for each sub-overlap in a collidable pair involving compound collidables. If neither collidable in a pair is compound, this will not be called.
            /// 对于包含复合的对,如果前面对AllowContactGeneration的调用为拥有对返回false,则不会调用此函数。请注意,有可能
            /// 如果同一子对启用了连续冲突检测,则该函数将被调用两次;
            /// 在触点生成测试之前运行的CCD扫描测试在执行子对测试之前也会询问。</MARKEMS>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB)
            {
                // 这类似于上面的顶级宽相回调。它是由生成前的窄相调用的
                // 父形状中的子项之间的子对。
                // 只有涉及至少一个可以包含多个子形状的形状类型(如复合体)的成对调用才会被调用。
                return true;
            }

            /// <summary>
            /// 提供已为配对创建歧管的通知。提供了更改歧管详细信息的机会。
            /// </summary>
            /// <param name="workerIndex">创建此歧管的工作线程的索引。</param>
            /// <param name="Pair">在其间检测到歧管的一对可碰撞对象。</param>
            /// <param name="歧管">检测到的可碰撞对象之间的接触集。</param>
            /// <param name="pairMaterial">歧管的材料属性。</param>
            /// <return>如果应该为流形创建约束,则为True,否则为False。</return>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public unsafe bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : struct, IContactManifold<TManifold>
            {
                // IContactManifold参数包括用于访问联系人数据的函数,而不管歧管的底层类型是什么。
                // 如果您想要直接访问基础类型,则可以使用symold.Convex属性和类似于Unsafe.As<TManifold,ConvexContactManifold或NonvexContactManifold>(引用流形)的强制转换。

                // 引擎不定义任何每个实体的材质属性。相反,所有材质查找和混合操作都由回调处理。
                // 出于本演示的目的,我们将对所有对使用相同的设置。
                // (请注意,没有弹力性属性！有关更多详细信息,请参见此处：https://github.com/bepu/bepuphysics2/issues/3)
                pairMaterial.FrictionCoefficient = 1f;
                pairMaterial.MaximumRecoveryVelocity = 2f;
                pairMaterial.SpringSettings = new SpringSettings(30, 1);
                // 出于演示目的,始终会生成接触约束。
                return true;
            }

            /// <summary>
            /// 提供已在复合(包括)对中的两个可碰撞对象的子项之间创建流形的通知。
            /// 提供了更改歧管详细信息的机会。
            /// </summary>
            /// <param name="workerIndex">创建此歧管的工作线程的索引。</param>
            /// <param name="Pair">在其间检测到歧管的一对可碰撞对象。</param>
            /// <param name="Child IndexA">对中可碰撞A的子项的索引。如果可碰撞A不是复合的,则该值始终为0。</param>
            /// <param name="Child IndexB">对中可碰撞B的子项的索引。如果可碰撞B不是复合的,则该值始终为0。</param>
            /// <param name="歧管">检测到的可碰撞对象之间的接触集。</param>
            /// <return>如果应将此流形考虑用于约束生成,则为True,否则为False。</return>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
            {
                return true;
            }

            /// <summary>
            /// 释放回调持有的所有资源。在处理它时由拥有的窄阶段调用。
            /// </summary>
            public void Dispose()
            {
            }
        }

        // 请注意,引擎不需要任何特定形式的重力-它与所有联系回调一样,由回调管理。
        public struct PoseIntegratorCallbacks : IPoseIntegratorCallbacks
        {
            public Vector3 Gravity;
            Vector3 gravityDt;

            /// <summary>
            /// 获取姿势积分器应如何处理角速度积分。
            /// </summary>
            public AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving; // 在这个演示中不关心保真度！

            public PoseIntegratorCallbacks(Vector3 gravity) : this()
            {
                Gravity = gravity;
            }

            /// <summary>
            /// 在集成模拟的活动实体之前调用。当与子步进计时器一起使用时,可以使用不同的时间步长值在每帧中多次调用此函数。
            /// </summary>
            /// <param name="dt">当前时间步持续时间。</param>
            public void PrepareForIntegration(float dt)
            {
                // 没有理由为每个人重新计算重力*dt;只需提前缓存即可。
                gravityDt = Gravity * dt;
            }

            /// <summary>
            /// 在实体集成期间,为模拟中的每个活动实体调用回调。
            /// </summary>
            /// <param name="bodyIndex">要访问的正文的索引。</param>
            /// <param name="pose">正文的当前姿势。</param>
            /// <param name="localInertia">身体当前的局部惯性。</param>
            /// <param name="workerIndex">处理此正文的工作线程的索引。</param>
            /// <param name="ocity">对要积分的物体当前速度的引用。</param>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void IntegrateVelocity(int bodyIndex, in RigidPose pose, in BodyInertia localInertia, int workerIndex, ref BodyVelocity velocity)
            {
                // 请注意,我们避免加速运动学。运动学是指任何质量为零的物体(即质量为~无穷大的物体)。没有任何力量可以移动它们。
                if (localInertia.InverseMass > 0)
                {
                    velocity.Linear = velocity.Linear + gravityDt;
                }
            }

        }

        public static void Run()
        {
            // 缓冲池是引擎要使用的原始内存BLOB的来源。
            var bufferPool = new BufferPool();
            // 请注意,您还可以使用不同的ITimdestepper实现来控制内部阶段执行的顺序。
            // PositionFirstTimeStepper在技术意义上是最简单的时步模式,但是由于它将速度整合到帧开始时的位置,
            // 在碰撞检测或解算器有机会介入之前,将对时间步长之外的直接修改的速度进行积分。
            // PositionLastTimestpper以极小的开销为代价,首先运行冲突检测和解算器,从而避免了这种情况。
            // (您可以通过修改PositionFirstTimestpper的BeforeCollisionDetection回调中的速度来避免PositionFirstTimeDespper的问题
            // 而不是在时间步长之外,但这有点复杂。)
            var simulation = Simulation.Create(bufferPool, new NarrowPhaseCallbacks(), new PoseIntegratorCallbacks(new Vector3(0, -10, 0)), new PositionLastTimestepper());

            // 把球扔到一个大静电盒上。
            var sphere = new Sphere(1);
            sphere.ComputeInertia(1, out var sphereInertia);
            simulation.Bodies.Add(BodyDescription.CreateDynamic(new Vector3(0, 5, 0), sphereInertia, new CollidableDescription(simulation.Shapes.Add(sphere), 0.1f), new BodyActivityDescription(0.01f)));

            simulation.Statics.Add(new StaticDescription(new Vector3(0, 0, 0), new CollidableDescription(simulation.Shapes.Add(new Box(500, 1, 500)), 0.1f)));

            var threadDispatcher = new SimpleThreadDispatcher(Environment.ProcessorCount);

            // 现在采取100个时间步长！
            for (int i = 0; i < 100; ++i)
            {
                // 对于一个球的模拟来说,多线程是没有意义的,但是要启用多线程,只需传递一个IThreadDispatcher实例即可。
                // 如果不想使用多线程,请不要传递IThreadDispatcher。
                simulation.Timestep(0.01f, threadDispatcher);
            }

            // 如果您打算重用BufferPool,那么处置模拟是个好主意-它会将所有缓冲区返回到池中以供重用。
            // 在这里,我们处理它,但这并不是真正需要的;此后我们会立即清除BufferPool中保存的所有内存。
            // 请注意,未能处置缓冲池可能会导致内存泄漏。
            simulation.Dispose();
            threadDispatcher.Dispose();
            bufferPool.Clear();
        }
    }
}
